/*-------------------------------------------------------------------------
 *
 * ginvacuum.c
 *	  postgres GIN 的删除和清理例程
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/ginvacuum.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/ginxlog.h"
#include "access/xloginsert.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "postmaster/autovacuum.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/memutils.h"

struct GinVacuumState
{
	Relation	index;
	IndexBulkDeleteResult *result;
	IndexBulkDeleteCallback callback;
	void	   *callback_state;
	GinState	ginstate;
	BufferAccessStrategy strategy;
	MemoryContext tmpCxt;
};

/*
 * 清理未压缩的发布列表。必须保留的大小可以指定为项目数(nitems)。
 *
 * 如果没有项目需要删除，则返回NULL。否则返回一个新分配的数组，包含剩余项目。剩余项目的数量通过*nremaining返回。
 */
ItemPointer ginVacuumItemPointers(GinVacuumState *fc_gvs, ItemPointerData *fc_items,
					  int fc_nitem, int *fc_nremaining)
{
	int			fc_i,
				fc_remaining = 0;
	ItemPointer fc_tmpitems = NULL;

	/*
	 * 遍历TID数组
	 */
	for (fc_i = 0; fc_i < fc_nitem; fc_i++)
	{
		if (fc_gvs->callback(fc_items + fc_i, fc_gvs->callback_state))
		{
			fc_gvs->result->tuples_removed += 1;
			if (!fc_tmpitems)
			{
				/*
				 * 第一个要删除的TID：分配内存以容纳
				 * 剩余项目。
				 */
				fc_tmpitems = palloc(sizeof(ItemPointerData) * fc_nitem);
				memcpy(fc_tmpitems, fc_items, sizeof(ItemPointerData) * fc_i);
			}
		}
		else
		{
			fc_gvs->result->num_index_tuples += 1;
			if (fc_tmpitems)
				fc_tmpitems[fc_remaining] = fc_items[fc_i];
			fc_remaining++;
		}
	}

	*fc_nremaining = fc_remaining;
	return fc_tmpitems;
}

/*
 * 为清理条目树叶页面创建WAL记录。
 */
static void fc_xlogVacuumPage(Relation fc_index, Buffer fc_buffer)
{
	Page		fc_page = BufferGetPage(fc_buffer);
	XLogRecPtr	fc_recptr;

	/* 这仅适用于条目树叶页面。 */
	Assert(!GinPageIsData(fc_page));
	Assert(GinPageIsLeaf(fc_page));

	if (!RelationNeedsWAL(fc_index))
		return;

	/*
	 * 始终创建完整的图像，我们不再跟踪页面的
	 * 更改。这显然可以改进...
	 */
	XLogBeginInsert();
	XLogRegisterBuffer(0, fc_buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);

	fc_recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
	PageSetLSN(fc_page, fc_recptr);
}


typedef struct DataPageDeleteStack
{
	struct DataPageDeleteStack *child;
	struct DataPageDeleteStack *parent;

	BlockNumber blkno;			/* 当前块编号 */
	Buffer		leftBuffer;		/* 固定并锁定的右侧非删除页面
								 * 在左侧 */
	bool		isRoot;
} DataPageDeleteStack;


/*
 * 删除发布树页面。
 */
static void fc_ginDeletePage(GinVacuumState *fc_gvs, BlockNumber fc_deleteBlkno, BlockNumber fc_leftBlkno,
			  BlockNumber fc_parentBlkno, OffsetNumber fc_myoff, bool fc_isParentRoot)
{
	Buffer		fc_dBuffer;
	Buffer		fc_lBuffer;
	Buffer		fc_pBuffer;
	Page		fc_page,
				fc_parentPage;
	BlockNumber fc_rightlink;

	/*
	 * 仅当父页面中的某个页面持有
	 * 独占清理锁时，这个函数才必须被调用。这样可以保证在这个子树中不会有插入操作。
	 * 调用者还在可删除页面、父页面和左侧页面上获取独占锁。
	 */
	fc_lBuffer = ReadBufferExtended(fc_gvs->index, MAIN_FORKNUM, fc_leftBlkno,
								 RBM_NORMAL, fc_gvs->strategy);
	fc_dBuffer = ReadBufferExtended(fc_gvs->index, MAIN_FORKNUM, fc_deleteBlkno,
								 RBM_NORMAL, fc_gvs->strategy);
	fc_pBuffer = ReadBufferExtended(fc_gvs->index, MAIN_FORKNUM, fc_parentBlkno,
								 RBM_NORMAL, fc_gvs->strategy);

	fc_page = BufferGetPage(fc_dBuffer);
	fc_rightlink = GinPageGetOpaque(fc_page)->rightlink;

	/*
	 * 任何原本要进入叶块的插入现在将转入其
	 * 右侧兄弟。
	 */
	PredicateLockPageCombine(fc_gvs->index, fc_deleteBlkno, fc_rightlink);

	START_CRIT_SECTION();

	/* 通过更改左侧兄弟的右链接来解除页面链接 */
	fc_page = BufferGetPage(fc_lBuffer);
	GinPageGetOpaque(fc_page)->rightlink = fc_rightlink;

	/* 从父页面删除下链 */
	fc_parentPage = BufferGetPage(fc_pBuffer);
#ifdef USE_ASSERT_CHECKING
	do
	{
		PostingItem *fc_tod = GinDataPageGetPostingItem(fc_parentPage, fc_myoff);

		Assert(PostingItemGetBlockNumber(fc_tod) == fc_deleteBlkno);
	} while (0);
#endif
	GinPageDeletePostingItem(fc_parentPage, fc_myoff);

	fc_page = BufferGetPage(fc_dBuffer);

	/*
	 * 我们不应该更改右链接字段，以保持
	 * 正在运行的搜索扫描的可工作性。
	 */

	/*
	 * 将页面标记为已删除，并记住最后一个可以知道其
	 * 地址的xid。
	 */
	GinPageSetDeleted(fc_page);
	GinPageSetDeleteXid(fc_page, ReadNextTransactionId());

	MarkBufferDirty(fc_pBuffer);
	MarkBufferDirty(fc_lBuffer);
	MarkBufferDirty(fc_dBuffer);

	if (RelationNeedsWAL(fc_gvs->index))
	{
		XLogRecPtr	fc_recptr;
		ginxlogDeletePage fc_data;

		/*
		 * 我们不能为已删除页面传递REGBUF_STANDARD，因为在
		 * 9.4之前的版本中我们没有设置pd_lower。页面可能是
		 * 从旧版本进行二进制升级的，因此pd_lower可能没有
		 * 正确设置。同样适用于左侧页面，但是从
		 * 父页面删除项更新了其pd_lower，因此在这一点上
		 * 我们知道这是可以的。
		 */
		XLogBeginInsert();
		XLogRegisterBuffer(0, fc_dBuffer, 0);
		XLogRegisterBuffer(1, fc_pBuffer, REGBUF_STANDARD);
		XLogRegisterBuffer(2, fc_lBuffer, 0);

		fc_data.parentOffset = fc_myoff;
		fc_data.rightLink = GinPageGetOpaque(fc_page)->rightlink;
		fc_data.deleteXid = GinPageGetDeleteXid(fc_page);

		XLogRegisterData((char *) &fc_data, sizeof(ginxlogDeletePage));

		fc_recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE);
		PageSetLSN(fc_page, fc_recptr);
		PageSetLSN(fc_parentPage, fc_recptr);
		PageSetLSN(BufferGetPage(fc_lBuffer), fc_recptr);
	}

	ReleaseBuffer(fc_pBuffer);
	ReleaseBuffer(fc_lBuffer);
	ReleaseBuffer(fc_dBuffer);

	END_CRIT_SECTION();

	fc_gvs->result->pages_newly_deleted++;
	fc_gvs->result->pages_deleted++;
}


/*
 * 扫描发布树并删除空页面。调用者必须锁定根页面以进行
 * 清理。在从根到当前页面的扫描路径上保持独占锁定。
 * 同时保持左侧页面的独占锁定，因为ginDeletePage()
 * 需要它。如果我们稍后尝试重新锁定左侧页面，可能会与
 * ginStepRight()发生死锁。
 */
static bool fc_ginScanToDelete(GinVacuumState *fc_gvs, BlockNumber fc_blkno, bool fc_isRoot,
				DataPageDeleteStack *fc_parent, OffsetNumber fc_myoff)
{
	DataPageDeleteStack *fc_me;
	Buffer		fc_buffer;
	Page		fc_page;
	bool		fc_meDelete = false;
	bool		fc_isempty;

	if (fc_isRoot)
	{
		fc_me = fc_parent;
	}
	else
	{
		if (!fc_parent->child)
		{
			fc_me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
			fc_me->parent = fc_parent;
			fc_parent->child = fc_me;
			fc_me->leftBuffer = InvalidBuffer;
		}
		else
			fc_me = fc_parent->child;
	}

	fc_buffer = ReadBufferExtended(fc_gvs->index, MAIN_FORKNUM, fc_blkno,
								RBM_NORMAL, fc_gvs->strategy);

	if (!fc_isRoot)
		LockBuffer(fc_buffer, GIN_EXCLUSIVE);

	fc_page = BufferGetPage(fc_buffer);

	Assert(GinPageIsData(fc_page));

	if (!GinPageIsLeaf(fc_page))
	{
		OffsetNumber fc_i;

		fc_me->blkno = fc_blkno;
		for (fc_i = FirstOffsetNumber; fc_i <= GinPageGetOpaque(fc_page)->maxoff; fc_i++)
		{
			PostingItem *fc_pitem = GinDataPageGetPostingItem(fc_page, fc_i);

			if (fc_ginScanToDelete(fc_gvs, PostingItemGetBlockNumber(fc_pitem), false, fc_me, fc_i))
				fc_i--;
		}

		if (GinPageRightMost(fc_page) && BufferIsValid(fc_me->child->leftBuffer))
		{
			UnlockReleaseBuffer(fc_me->child->leftBuffer);
			fc_me->child->leftBuffer = InvalidBuffer;
		}
	}

	if (GinPageIsLeaf(fc_page))
		fc_isempty = GinDataLeafPageIsEmpty(fc_page);
	else
		fc_isempty = GinPageGetOpaque(fc_page)->maxoff < FirstOffsetNumber;

	if (fc_isempty)
	{
		/* 我们从不删除左侧或右侧的分支 */
		if (BufferIsValid(fc_me->leftBuffer) && !GinPageRightMost(fc_page))
		{
			Assert(!fc_isRoot);
			fc_ginDeletePage(fc_gvs, fc_blkno, BufferGetBlockNumber(fc_me->leftBuffer),
						  fc_me->parent->blkno, fc_myoff, fc_me->parent->isRoot);
			fc_meDelete = true;
		}
	}

	if (!fc_meDelete)
	{
		if (BufferIsValid(fc_me->leftBuffer))
			UnlockReleaseBuffer(fc_me->leftBuffer);
		fc_me->leftBuffer = fc_buffer;
	}
	else
	{
		if (!fc_isRoot)
			LockBuffer(fc_buffer, GIN_UNLOCK);

		ReleaseBuffer(fc_buffer);
	}

	if (fc_isRoot)
		ReleaseBuffer(fc_buffer);

	return fc_meDelete;
}


/*
 * 扫描发布树叶，删除空元组。如果至少有一个空页面，则返回true。
 */
static bool fc_ginVacuumPostingTreeLeaves(GinVacuumState *fc_gvs, BlockNumber fc_blkno)
{
	Buffer		fc_buffer;
	Page		fc_page;
	bool		fc_hasVoidPage = false;
	MemoryContext fc_oldCxt;

	/* 查找发布树的最左叶页面并以独占模式锁定它 */
	while (true)
	{
		PostingItem *fc_pitem;

		fc_buffer = ReadBufferExtended(fc_gvs->index, MAIN_FORKNUM, fc_blkno,
									RBM_NORMAL, fc_gvs->strategy);
		LockBuffer(fc_buffer, GIN_SHARE);
		fc_page = BufferGetPage(fc_buffer);

		Assert(GinPageIsData(fc_page));

		if (GinPageIsLeaf(fc_page))
		{
			LockBuffer(fc_buffer, GIN_UNLOCK);
			LockBuffer(fc_buffer, GIN_EXCLUSIVE);
			break;
		}

		Assert(PageGetMaxOffsetNumber(fc_page) >= FirstOffsetNumber);

		fc_pitem = GinDataPageGetPostingItem(fc_page, FirstOffsetNumber);
		fc_blkno = PostingItemGetBlockNumber(fc_pitem);
		Assert(fc_blkno != InvalidBlockNumber);

		UnlockReleaseBuffer(fc_buffer);
	}

	/* 使用右链接迭代所有发布树叶并清理它们 */
	while (true)
	{
		fc_oldCxt = MemoryContextSwitchTo(fc_gvs->tmpCxt);
		ginVacuumPostingTreeLeaf(fc_gvs->index, fc_buffer, fc_gvs);
		MemoryContextSwitchTo(fc_oldCxt);
		MemoryContextReset(fc_gvs->tmpCxt);

		if (GinDataLeafPageIsEmpty(fc_page))
			fc_hasVoidPage = true;

		fc_blkno = GinPageGetOpaque(fc_page)->rightlink;

		UnlockReleaseBuffer(fc_buffer);

		if (fc_blkno == InvalidBlockNumber)
			break;

		fc_buffer = ReadBufferExtended(fc_gvs->index, MAIN_FORKNUM, fc_blkno,
									RBM_NORMAL, fc_gvs->strategy);
		LockBuffer(fc_buffer, GIN_EXCLUSIVE);
		fc_page = BufferGetPage(fc_buffer);
	}

	return fc_hasVoidPage;
}

static void fc_ginVacuumPostingTree(GinVacuumState *fc_gvs, BlockNumber fc_rootBlkno)
{
	if (fc_ginVacuumPostingTreeLeaves(fc_gvs, fc_rootBlkno))
	{
		/*
		 * 至少有一个空页面。因此，我们必须重新扫描树
		 * 删除空页面。
		 */
		Buffer		fc_buffer;
		DataPageDeleteStack fc_root,
				   *fc_ptr,
				   *fc_tmp;

		fc_buffer = ReadBufferExtended(fc_gvs->index, MAIN_FORKNUM, fc_rootBlkno,
									RBM_NORMAL, fc_gvs->strategy);

		/*
		 * 锁定发布树根以进行清理，以确保没有
		 * 并发插入。
		 */
		LockBufferForCleanup(fc_buffer);

		memset(&fc_root, 0, sizeof(DataPageDeleteStack));
		fc_root.leftBuffer = InvalidBuffer;
		fc_root.isRoot = true;

		fc_ginScanToDelete(fc_gvs, fc_rootBlkno, true, &fc_root, InvalidOffsetNumber);

		fc_ptr = fc_root.child;

		while (fc_ptr)
		{
			fc_tmp = fc_ptr->child;
			pfree(fc_ptr);
			fc_ptr = fc_tmp;
		}

		UnlockReleaseBuffer(fc_buffer);
	}
}

/*
 * 返回修改后的页面，如果页面未修改，则返回NULL。
 * 函数在发生第一次更改之前一直使用原始页面，
 * 然后页面被复制到临时页面中。
 */
static Page fc_ginVacuumEntryPage(GinVacuumState *fc_gvs, Buffer fc_buffer, BlockNumber *fc_roots, uint32 *fc_nroot)
{
	Page		fc_origpage = BufferGetPage(fc_buffer),
				fc_tmppage;
	OffsetNumber fc_i,
				fc_maxoff = PageGetMaxOffsetNumber(fc_origpage);

	fc_tmppage = fc_origpage;

	*fc_nroot = 0;

	for (fc_i = FirstOffsetNumber; fc_i <= fc_maxoff; fc_i++)
	{
		IndexTuple	fc_itup = (IndexTuple) PageGetItem(fc_tmppage, PageGetItemId(fc_tmppage, fc_i));

		if (GinIsPostingTree(fc_itup))
		{
			/*
			 * 存储发布树的根以便进一步处理，我们不能
			 * 现在进行清理，因为存在与扫描/插入的死锁风险
			 */
			fc_roots[*fc_nroot] = GinGetDownlink(fc_itup);
			(*fc_nroot)++;
		}
		else if (GinGetNPosting(fc_itup) > 0)
		{
			int			fc_nitems;
			ItemPointer fc_items_orig;
			bool		fc_free_items_orig;
			ItemPointer fc_items;

			/* 从元组中获取项目指针列表。 */
			if (GinItupIsCompressed(fc_itup))
			{
				fc_items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(fc_itup), &fc_nitems);
				fc_free_items_orig = true;
			}
			else
			{
				fc_items_orig = (ItemPointer) GinGetPosting(fc_itup);
				fc_nitems = GinGetNPosting(fc_itup);
				fc_free_items_orig = false;
			}

			/* 从列表中删除任何需要被清理的项目。 */
			fc_items = ginVacuumItemPointers(fc_gvs, fc_items_orig, fc_nitems, &fc_nitems);

			if (fc_free_items_orig)
				pfree(fc_items_orig);

			/* 如果有任何项目指针被移除，则重新创建元组。 */
			if (fc_items)
			{
				OffsetNumber fc_attnum;
				Datum		fc_key;
				GinNullCategory fc_category;
				GinPostingList *fc_plist;
				int			fc_plistsize;

				if (fc_nitems > 0)
				{
					fc_plist = ginCompressPostingList(fc_items, fc_nitems, GinMaxItemSize, NULL);
					fc_plistsize = SizeOfGinPostingList(fc_plist);
				}
				else
				{
					fc_plist = NULL;
					fc_plistsize = 0;
				}

				/*
				 * 如果我们已经创建了一个临时页面，则就地进行更改
				 */
				if (fc_tmppage == fc_origpage)
				{
					/*
					 * 在第一次差异时，创建页面的临时副本，并将元组的发布列表复制到其中。
					 */
					fc_tmppage = PageGetTempPageCopy(fc_origpage);

					/* 将指针设置到新页面 */
					fc_itup = (IndexTuple) PageGetItem(fc_tmppage, PageGetItemId(fc_tmppage, fc_i));
				}

				fc_attnum = gintuple_get_attrnum(&fc_gvs->ginstate, fc_itup);
				fc_key = gintuple_get_key(&fc_gvs->ginstate, fc_itup, &fc_category);
				fc_itup = GinFormTuple(&fc_gvs->ginstate, fc_attnum, fc_key, fc_category,
									(char *) fc_plist, fc_plistsize,
									fc_nitems, true);
				if (fc_plist)
					pfree(fc_plist);
				PageIndexTupleDelete(fc_tmppage, fc_i);

				if (PageAddItem(fc_tmppage, (Item) fc_itup, IndexTupleSize(fc_itup), fc_i, false, false) != fc_i)
					elog(ERROR, "failed to add item to index page in \"%s\"",
						 RelationGetRelationName(fc_gvs->index));

				pfree(fc_itup);
				pfree(fc_items);
			}
		}
	}

	return (fc_tmppage == fc_origpage) ? NULL : fc_tmppage;
}

IndexBulkDeleteResult * ginbulkdelete(IndexVacuumInfo *fc_info, IndexBulkDeleteResult *fc_stats,
			  IndexBulkDeleteCallback fc_callback, void *fc_callback_state)
{
	Relation	fc_index = fc_info->index;
	BlockNumber fc_blkno = GIN_ROOT_BLKNO;
	GinVacuumState fc_gvs;
	Buffer		fc_buffer;
	BlockNumber fc_rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
	uint32		fc_nRoot;

	fc_gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
									   "Gin vacuum temporary context",
									   ALLOCSET_DEFAULT_SIZES);
	fc_gvs.index = fc_index;
	fc_gvs.callback = fc_callback;
	fc_gvs.callback_state = fc_callback_state;
	fc_gvs.strategy = fc_info->strategy;
	initGinState(&fc_gvs.ginstate, fc_index);

	/* 第一次通过？ */
	if (fc_stats == NULL)
	{
		/* 是的，因此将统计信息初始化为零 */
		fc_stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));

		/*
		 * 并清理任何待处理的插入
		 */
		ginInsertCleanup(&fc_gvs.ginstate, !IsAutoVacuumWorkerProcess(),
						 false, true, fc_stats);
	}

	/* 每次我们都会重新计算元组 */
	fc_stats->num_index_tuples = 0;
	fc_gvs.result = fc_stats;

	fc_buffer = ReadBufferExtended(fc_index, MAIN_FORKNUM, fc_blkno,
								RBM_NORMAL, fc_info->strategy);

	/* 查找叶子页面 */
	for (;;)
	{
		Page		fc_page = BufferGetPage(fc_buffer);
		IndexTuple	fc_itup;

		LockBuffer(fc_buffer, GIN_SHARE);

		Assert(!GinPageIsData(fc_page));

		if (GinPageIsLeaf(fc_page))
		{
			LockBuffer(fc_buffer, GIN_UNLOCK);
			LockBuffer(fc_buffer, GIN_EXCLUSIVE);

			if (fc_blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(fc_page))
			{
				LockBuffer(fc_buffer, GIN_UNLOCK);
				continue;		/* 再检查一次 */
			}
			break;
		}

		Assert(PageGetMaxOffsetNumber(fc_page) >= FirstOffsetNumber);

		fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, FirstOffsetNumber));
		fc_blkno = GinGetDownlink(fc_itup);
		Assert(fc_blkno != InvalidBlockNumber);

		UnlockReleaseBuffer(fc_buffer);
		fc_buffer = ReadBufferExtended(fc_index, MAIN_FORKNUM, fc_blkno,
									RBM_NORMAL, fc_info->strategy);
	}

	/* 现在我们找到的条目是 BTree 中的最左页面 */

	for (;;)
	{
		Page		fc_page = BufferGetPage(fc_buffer);
		Page		fc_resPage;
		uint32		fc_i;

		Assert(!GinPageIsData(fc_page));

		fc_resPage = fc_ginVacuumEntryPage(&fc_gvs, fc_buffer, fc_rootOfPostingTree, &fc_nRoot);

		fc_blkno = GinPageGetOpaque(fc_page)->rightlink;

		if (fc_resPage)
		{
			START_CRIT_SECTION();
			PageRestoreTempPage(fc_resPage, fc_page);
			MarkBufferDirty(fc_buffer);
			fc_xlogVacuumPage(fc_gvs.index, fc_buffer);
			UnlockReleaseBuffer(fc_buffer);
			END_CRIT_SECTION();
		}
		else
		{
			UnlockReleaseBuffer(fc_buffer);
		}

		vacuum_delay_point();

		for (fc_i = 0; fc_i < fc_nRoot; fc_i++)
		{
			fc_ginVacuumPostingTree(&fc_gvs, fc_rootOfPostingTree[fc_i]);
			vacuum_delay_point();
		}

		if (fc_blkno == InvalidBlockNumber)	/* 最右边的页面 */
			break;

		fc_buffer = ReadBufferExtended(fc_index, MAIN_FORKNUM, fc_blkno,
									RBM_NORMAL, fc_info->strategy);
		LockBuffer(fc_buffer, GIN_EXCLUSIVE);
	}

	MemoryContextDelete(fc_gvs.tmpCxt);

	return fc_gvs.result;
}

IndexBulkDeleteResult * ginvacuumcleanup(IndexVacuumInfo *fc_info, IndexBulkDeleteResult *fc_stats)
{
	Relation	fc_index = fc_info->index;
	bool		fc_needLock;
	BlockNumber fc_npages,
				fc_blkno;
	BlockNumber fc_totFreePages;
	GinState	fc_ginstate;
	GinStatsData fc_idxStat;

	/*
	 * 在自动清理分析中，我们希望清理待处理的插入。
	 * 否则，仅 ANALYZE 调用是无操作。
	 */
	if (fc_info->analyze_only)
	{
		if (IsAutoVacuumWorkerProcess())
		{
			initGinState(&fc_ginstate, fc_index);
			ginInsertCleanup(&fc_ginstate, false, true, true, fc_stats);
		}
		return fc_stats;
	}

	/*
	 * 设置全零的统计信息，并清理待处理的插入（如果没有调用 ginbulkdelete）
	 */
	if (fc_stats == NULL)
	{
		fc_stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
		initGinState(&fc_ginstate, fc_index);
		ginInsertCleanup(&fc_ginstate, !IsAutoVacuumWorkerProcess(),
						 false, true, fc_stats);
	}

	memset(&fc_idxStat, 0, sizeof(fc_idxStat));

	/*
	 * XXX 我们总是将堆元组计数报告为索引
	 * 条目的数量。如果索引是部分的，这是不正确的，但很难
	 * 判断有多少个不同的堆条目由 GIN 索引引用。
	 */
	fc_stats->num_index_tuples = Max(fc_info->num_heap_tuples, 0);
	fc_stats->estimated_count = fc_info->estimated_count;

	/*
	 * 需要锁，除非它是本后端本地的。
	 */
	fc_needLock = !RELATION_IS_LOCAL(fc_index);

	if (fc_needLock)
		LockRelationForExtension(fc_index, ExclusiveLock);
	fc_npages = RelationGetNumberOfBlocks(fc_index);
	if (fc_needLock)
		UnlockRelationForExtension(fc_index, ExclusiveLock);

	fc_totFreePages = 0;

	for (fc_blkno = GIN_ROOT_BLKNO; fc_blkno < fc_npages; fc_blkno++)
	{
		Buffer		fc_buffer;
		Page		fc_page;

		vacuum_delay_point();

		fc_buffer = ReadBufferExtended(fc_index, MAIN_FORKNUM, fc_blkno,
									RBM_NORMAL, fc_info->strategy);
		LockBuffer(fc_buffer, GIN_SHARE);
		fc_page = (Page) BufferGetPage(fc_buffer);

		if (GinPageIsRecyclable(fc_page))
		{
			Assert(fc_blkno != GIN_ROOT_BLKNO);
			RecordFreeIndexPage(fc_index, fc_blkno);
			fc_totFreePages++;
		}
		else if (GinPageIsData(fc_page))
		{
			fc_idxStat.nDataPages++;
		}
		else if (!GinPageIsList(fc_page))
		{
			fc_idxStat.nEntryPages++;

			if (GinPageIsLeaf(fc_page))
				fc_idxStat.nEntries += PageGetMaxOffsetNumber(fc_page);
		}

		UnlockReleaseBuffer(fc_buffer);
	}

	/* 使用准确的页面和条目计数更新元数据页面 */
	fc_idxStat.nTotalPages = fc_npages;
	ginUpdateStats(fc_info->index, &fc_idxStat, false);

	/* 最后，清理 FSM */
	IndexFreeSpaceMapVacuum(fc_info->index);

	fc_stats->pages_free = fc_totFreePages;

	if (fc_needLock)
		LockRelationForExtension(fc_index, ExclusiveLock);
	fc_stats->num_pages = RelationGetNumberOfBlocks(fc_index);
	if (fc_needLock)
		UnlockRelationForExtension(fc_index, ExclusiveLock);

	return fc_stats;
}

/*
 * 返回页面是否可以安全被回收。
 */
bool GinPageIsRecyclable(Page fc_page)
{
	TransactionId fc_delete_xid;

	if (PageIsNew(fc_page))
		return true;

	if (!GinPageIsDeleted(fc_page))
		return false;

	fc_delete_xid = GinPageGetDeleteXid(fc_page);

	if (!TransactionIdIsValid(fc_delete_xid))
		return true;

	/*
	 * 如果没有后端仍然可以将 delete_xid 视为正在运行，则与 ginDeletePage() 的所有并发扫描必须已经完成。
	 */
	return GlobalVisCheckRemovableXid(NULL, fc_delete_xid);
}
